/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package cegg.graph.export;

import cegg.graph.Graph;
import cegg.graph.connection.AndConnection;
import cegg.graph.connection.Connection;
import cegg.graph.connection.EqualConnection;
import cegg.graph.connection.NotConnection;
import cegg.graph.connection.OrConnection;
import cegg.graph.dependency.Dependency;
import cegg.graph.dependency.NegativeDependency;
import cegg.graph.dependency.PositiveDependency;
import cegg.graph.restriction.AtLeastOneRestriction;
import cegg.graph.restriction.AtMostOneRestriction;
import cegg.graph.restriction.ExactOneRestriction;
import cegg.graph.restriction.Restriction;
import cegg.graph.vertex.EffectVertex;
import cegg.graph.vertex.IntermediateVertex;
import cegg.graph.vertex.ReasonVertex;
import cegg.graph.vertex.Vertex;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.omg.CORBA.INTERNAL;

/**
 *
 * @author kumper
 */
public class DecisionTable
{

    private Graph graph;
    private int[][] table;
    private int nbOfRows;
    private int nbOfColumns;
    private int nbOfReasons;
    private int nbOfEffects;

    public DecisionTable(Graph graph)
    {
        this.graph = graph;

        this.nbOfReasons = this.graph.countReasons();
        this.nbOfEffects = this.graph.countEffects();

        this.nbOfRows = nbOfReasons + nbOfEffects;
        this.nbOfColumns = ((int) Math.pow(2, nbOfReasons))+1;

//        System.out.println("Number of rows: " + nbOfRows);
//        System.out.println("Number of columns: " + nbOfColumns);

        this.table = new int[nbOfRows][nbOfColumns];

        this.init();

        System.out.println(this);

        this.optimize();

        System.out.println(this);

        this.evalEffects();

        System.out.println(this);
    }

    private void init()
    {
//        System.out.println("Table length: " + table.length);

        //fill first column with reasons
        for (int j=0; j<nbOfReasons; j++)
        {
            table[j][0] = j+1;
        }

        //fill first column with effects
        for (int j=nbOfReasons; j<nbOfReasons + nbOfEffects; j++)
        {
            table[j][0] = j+51-nbOfReasons;
        }

        //fill next columns in reason rows with all possible data
        int[] tmp = new int[nbOfReasons];
        for (int i=0; i<tmp.length; i++) tmp[i]=0;

        for (int j=1; j<nbOfColumns; j++)
        {
            for (int k=0;k<tmp.length; k++) table[k][j]=tmp[k];

            tmp[0] = (tmp[0]+1)%2;
            if (tmp[0] == 0)
            {
                int lastTrueIndex=0;

                do
                {
                    lastTrueIndex++;
                    if (lastTrueIndex == tmp.length) break;
                    tmp[lastTrueIndex] = (tmp[lastTrueIndex]+1)%2;
                } while (tmp[lastTrueIndex]==0);
            }
        }

//        System.out.println(this);
    }

    //TODO: remove columns, which are incorrect
    //depending on the dependency and restriction connections
    private void optimize()
    {
        Set<Integer> deletedColumns = new HashSet<Integer>();
        Set<Restriction> restrictions = this.graph.getRestrictionSet();
        Set<Dependency> dependencies = this.graph.getDependencySet();

        for (int col=1; col<nbOfColumns; col++)
        {
            for (final Restriction restriction : restrictions)
            {
                if (restriction instanceof AtMostOneRestriction)
                {
                    //we are counting number of true(1) data fields
                    //because we have to remove the columns where we have more
                    //than one true(1) values
                    int nbOfTrue=0;
                    for (final Vertex vertex : restriction.getVertexSet())
                    {
                        if (this.getValueForVertex(vertex.getId(), col) == 1)
                        {
                            nbOfTrue++;
                        }
                    }

                    if (nbOfTrue>1)
                    {
                        deletedColumns.add(col);
                    }
                }

                if (restriction instanceof AtLeastOneRestriction)
                {
                    //we are counting number of true(1) data fields
                    //because we have to remove the columns where we have not
                    //more than one true(1) values
                    int nbOfTrue=0;
                    for (final Vertex vertex : restriction.getVertexSet())
                    {
                        if (this.getValueForVertex(vertex.getId(), col) == 1)
                        {
                            nbOfTrue++;
                        }
                    }

                    if (nbOfTrue<1)
                    {
                        deletedColumns.add(col);
                    }
                }

                if (restriction instanceof ExactOneRestriction)
                {
                    //we are counting number of true(1) data fields
                    //because we have to remove the columns where we have not
                    //exact one true(1)
                    int nbOfTrue=0;
                    for (final Vertex vertex : restriction.getVertexSet())
                    {
                        if (this.getValueForVertex(vertex.getId(), col) == 1)
                        {
                            nbOfTrue++;
                        }
                    }

                    if (nbOfTrue!=1)
                    {
                        deletedColumns.add(col);
                    }
                }
            }

            for (final Dependency dependency : dependencies)
            {
                int fromVertexValue =
                        this.getValueForVertex(dependency.getFrom().getId(),
                                               col);
                int toVertexValue =
                        this.getValueForVertex(dependency.getTo().getId(),
                                               col);

                if (dependency instanceof PositiveDependency)
                {
                    if ((fromVertexValue==1) &&
                        (toVertexValue==0))
                    {
                        deletedColumns.add(col);
                    }
                }

                if (dependency instanceof NegativeDependency)
                {
                    if ((fromVertexValue==1) &&
                        (toVertexValue==1))
                    {
                        deletedColumns.add(col);
                    }
                }
            }
        }

        int[][] newTable = new int[nbOfRows][nbOfColumns-deletedColumns.size()];

//        System.out.println("New table rows: " + newTable.length + "\n"
//                + "New table columns: " + newTable[0].length);
//        System.out.println("deletedColumns: " + deletedColumns);

        int columnsWritten = 1;
        for (int i=0; i<nbOfRows; i++)
        {
            columnsWritten = 1;
            for (int j=0; j<nbOfColumns; j++)
            {
                if (j==0)
                {
                    newTable[i][j]=table[i][j];
                }
                else
                {
                    if (!deletedColumns.contains(new Integer(j)))
                    {
//                        System.out.println("Copying column: " + j +" to " + columnsWritten);
                        newTable[i][columnsWritten]=table[i][j];
                        columnsWritten++;
                    }
                }
            }
        }

        this.table = newTable;

        this.nbOfColumns = this.table[0].length;

//        System.out.println(this);
    }

    //TODO: evaluate effects columns according to the graph
    private void evalEffects()
    {
//        Set<Integer> deletedColumns = new HashSet<Integer>();
//        Set<Restriction> restrictions = this.graph.getRestrictionSet();
//        Set<Dependency> dependencies = this.graph.getDependencySet();
        Set<Connection> connections = this.graph.getConnectionSet();
        Map<Integer, Integer> intermediateVertices = new HashMap<Integer, Integer>();

        for (int col=1; col<nbOfColumns; col++)
        {
            //ReasonVertex-->IntermediateVertex
            for (final Connection connection : connections)
            {
                Vertex fromVertex = connection.getStart();
                Vertex toVertex = connection.getEnd();

//                if ((fromVertex instanceof ReasonVertex) &&
//                    (toVertex instanceof EffectVertex))
                if ((fromVertex instanceof ReasonVertex) &&
                    (toVertex instanceof IntermediateVertex))
                {
                    if (connection instanceof EqualConnection)
                    {
//                        if (toVertex instanceof IntermediateVertex)
//                        {
                            intermediateVertices.put(toVertex.getId(),
                                this.getValueForVertex(fromVertex.getId(), col));
//                        }
//                        else
//                        {
//                            this.setValueForVertex(toVertex.getId(),
//                                    col,
//                                    this.getValueForVertex(fromVertex.getId(), col));
//                        }
                    }
                    if (connection instanceof NotConnection)
                    {
//                        if (toVertex instanceof IntermediateVertex)
//                        {
                            intermediateVertices.put(toVertex.getId(),
                                (this.getValueForVertex(fromVertex.getId(), col)+1)%2);
//                        }
//                        else
//                        {
//                            this.setValueForVertex(toVertex.getId(),
//                                    col,
//                                    (this.getValueForVertex(fromVertex.getId(), col)+1)%2);
//                        }
                    }
                    if (connection instanceof AndConnection)
                    {
                        int evaluatedValue = 1;
                        for (final Vertex vertex : this.graph.getAndVerticesForVertex(toVertex.getId()))
                        {
                            if (this.getValueForVertex(vertex.getId(), col) == 0)
                            {
                                evaluatedValue = 0;
                            }
                        }
//                        if (toVertex instanceof IntermediateVertex)
//                        {
                            intermediateVertices.put(toVertex.getId(),
                                evaluatedValue);
//                        }
//                        else
//                        {
//                            this.setValueForVertex(toVertex.getId(), col, evaluatedValue);
//                        }
                    }
                    if (connection instanceof OrConnection)
                    {
                        int evaluatedValue = 0;
                        for (final Vertex vertex : this.graph.getOrVerticesForVertex(toVertex.getId()))
                        {
                            if (this.getValueForVertex(vertex.getId(), col) == 1)
                            {
                                evaluatedValue = 1;
                            }
                        }
//                        if (toVertex instanceof IntermediateVertex)
//                        {
                            intermediateVertices.put(toVertex.getId(),
                                evaluatedValue);
//                        }
//                        else
//                        {
//                            this.setValueForVertex(toVertex.getId(), col, evaluatedValue);
//                        }
                    }
                }
            }

            intermediateVertices.put(50, 0);

            System.out.println("test: " + intermediateVertices.get(50));

            //ReasonVertex-->EffectVertex && IntermediateVertex-->EffectVertex
            for (final Connection connection : connections)
            {
                Vertex fromVertex = connection.getStart();
                Vertex toVertex = connection.getEnd();

                if (toVertex instanceof EffectVertex)
                {
                    if (connection instanceof EqualConnection)
                    {
                        int value = 0;
                        
                        if (fromVertex instanceof IntermediateVertex)
                        {
                            value = intermediateVertices.get(fromVertex.getId());
                        }
                        else
                        {
                            value = this.getValueForVertex(fromVertex.getId(), col);
                        }

                        this.setValueForVertex(toVertex.getId(),
                                col,
                                value);
                    }
                    if (connection instanceof NotConnection)
                    {
                        int value = 0;

                        if (fromVertex instanceof IntermediateVertex)
                        {
                            value = (intermediateVertices.get(fromVertex.getId())+1)%2;
                        }
                        else
                        {
                            value = (this.getValueForVertex(fromVertex.getId(), col)+1)%2;
                        }

                        this.setValueForVertex(toVertex.getId(),
                                col,
                                value);
                    }
                    if (connection instanceof AndConnection)
                    {
                        int evaluatedValue = 1;

                        for (final Vertex vertex : this.graph.getAndVerticesForVertex(toVertex.getId()))
                        {
                            int value = 0;

                            if (vertex instanceof IntermediateVertex)
                            {
                                value = intermediateVertices.get(vertex.getId());
                            }
                            else
                            {
                                value = this.getValueForVertex(vertex.getId(), col);
                            }

                            if (value == 0)
                            {
                                evaluatedValue = 0;
                            }
                        }

                        this.setValueForVertex(toVertex.getId(), col, evaluatedValue);
                    }
                    if (connection instanceof OrConnection)
                    {
                        int evaluatedValue = 0;

                        for (final Vertex vertex : this.graph.getOrVerticesForVertex(toVertex.getId()))
                        {
                            int value = 0;

                            if (vertex instanceof IntermediateVertex)
                            {
                                value = intermediateVertices.get(vertex.getId());
                            }
                            else
                            {
                                value = this.getValueForVertex(vertex.getId(), col);
                            }

                            if (value == 1)
                            {
                                evaluatedValue = 1;
                            }
                        }

                        this.setValueForVertex(toVertex.getId(), col, evaluatedValue);
                    }
                }
            }
        }
    }

    private int getValueForVertex(int id, int column)
    {
        //TODO: exception handling
        for (int i=0; i<nbOfRows; i++)
        {
            if (id==table[i][0])
                return table[i][column];
        }
        return -1;
    }

    private void setValueForVertex(int id, int column, int newValue)
    {
        //TODO: exception handling
        for (int i=0; i<nbOfRows; i++)
        {
            if (id==table[i][0])
            {
                table[i][column] = newValue;
            }
        }
    }

    @Override
    public String toString() {
        StringBuilder ret = new StringBuilder();

        for (int i=0; i<nbOfRows; i++)
        {
            for (int j=0; j<nbOfColumns; j++)
            {
                ret.append(table[i][j])
                   .append("; ");
            }
            ret.append("\n");
        }

        return ret.toString();
    }
}
